﻿/*
* @Author: z.p
* @Project: Zdotp
* @Date: 2024年12月30日
* @Description: kcpSession
*/

using System.Buffers;
using System.Net;
using System.Net.Sockets.Kcp;
using Zdotp.Logs;
using Zdotp.Net.Kcp.App;

namespace Zdotp.Net.Kcp;

public enum SessionState 
{
    Connected,
    DisConnected
}

public class KcpSession : IKcpCallback
{
    public uint SessionId { get; private set; }

    private readonly Action<byte[], IPEndPoint> _udpSender;
    private readonly IPEndPoint _remotePoint;
    private readonly System.Net.Sockets.Kcp.Kcp _kcp;
    private readonly CancellationTokenSource _cts;

    private readonly ZLogger _kcpLogger = LogHelper.CreateLogger("KcpSession");
    
    private SessionState _mSessionState = SessionState.DisConnected;
    public bool IsConnected() => _mSessionState == SessionState.Connected;
    
    public void Output(IMemoryOwner<byte> buffer, int avalidLength) 
    {
        using(buffer) 
        {
            var memory = buffer.Memory[..avalidLength];
            var bytes = memory.ToArray();
            _udpSender(bytes, _remotePoint);
        }
    }
    
    private readonly ZyKcpApp _kcpApp;

    public KcpSession(ZyKcpApp kcpApp, uint sid, Action<byte[], IPEndPoint> udpSender, IPEndPoint remotePoint)
    {
        _kcpApp = kcpApp;
        SessionId = sid;
        _udpSender = udpSender;
        _remotePoint = remotePoint;
        
        _kcp = new System.Net.Sockets.Kcp.Kcp(SessionId, this);
        _cts = new();
    }
    public void SessionStart()
    {
        _kcp.SetMtu(512);
        
        _mSessionState = SessionState.Connected;
        _kcpApp?.OnConnected(this);

        Task.Run(Update, _cts.Token);
    }
    public void ReceiveData(byte[] buffer) => _kcp.Input(buffer.AsSpan());

    public void SendMessage(byte[] message)
    {
        if(IsConnected()) _kcp.Send(message.AsSpan());
        else _kcpLogger.Warn("Session disconnected");
    }
    
    public void CloseSession() 
    {
        _cts?.Cancel();
        _kcpApp?.OnDisconnected(this);
        _mSessionState = SessionState.DisConnected;
        SessionId = 0;
    }

    private async void Update() 
    {
        try 
        {
            while(true) 
            {
                _kcp.Update(DateTime.UtcNow);
                while(true) 
                {
                    var len = _kcp.PeekSize();
                    if (len <= 0) break;
                    var buffer = new byte[len];
                    if (_kcp.Recv(buffer) < 0) continue;
                    _kcpApp?.OnReceive(this, buffer);
                }
                await Task.Delay(TimeSpan.FromSeconds(0.02f));
            }
        }
        catch(Exception e) 
        {
            _kcpLogger.Warn($"Session Update Exception:{e}");
        }
    }
}